package com.biz.primus.ms.stock.service;

import com.biz.primus.base.enums.DepotType;
import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.base.vo.BaseStockRespVo;
import com.biz.primus.base.vo.StockRespVo;
import com.biz.primus.common.enums.StockChannelEnum;
import com.biz.primus.common.utils.CopyUtils;
import com.biz.primus.model.depot.exception.DepotException;
import com.biz.primus.model.depot.vo.DepotRespVo;
import com.biz.primus.model.depot.vo.SearchDepotConditionVo;
import com.biz.primus.model.stock.exceptions.StockException;
import com.biz.primus.model.stock.vo.*;
import com.biz.primus.ms.stock.dao.ro.DepotStockRo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * StockService
 *
 * @author jia JKLiues
 * @date 2017年10月20日
 */
@Service
@Slf4j
public class StockService extends AbstractStockService {

    /**
     * 全量库存后置操作区域库存的刷新，门店商品索引刷新
     */
    @Override
    public void afterHandleStockAll(StockChannelEnum channelCode) {
        AssertUtils.notNull(channelCode, StockException.CHANNEL_CODE_IS_NULL);
        depotStockRedisDao.updateVersion(channelCode);
        log.info("全量库存处理结束, 发消息更新全量商品索引");
        super.pushStockChangeAllMessage();
    }

    /**
     * 根据门店编码和商品编码查库存（门店库存 和 区域库存+太原库存+总仓库存)
     *
     * @param reqVo 请求对象封装VO
     */
    public List<StockRespVo> getDepotStockInBatch(StocksReqVo reqVo) {
        List<String> productCodes = reqVo.getProductCodes();
        String depotCode = reqVo.getDepotCode();
        StockChannelEnum channelCode = reqVo.getChannelCode();
        AssertUtils.notNull(channelCode, StockException.CHANNEL_CODE_IS_NULL);
        List<String> homeDepots = this.getHomeDepots();
        List<String> centerDepots = this.getCenterDepots();
        Set<String> depotCodes = new HashSet<>();
        AssertUtils.isTrue(CollectionUtils.isNotEmpty(productCodes), StockException.PRODUCT_CODE_IS_NULL);
        List<DepotStockRo> depotStockRos;
        if (StringUtils.isNotBlank(depotCode)) {
            Optional<DepotRespVo> depotOption = depotFeignClient.findDepotByDepotCode(depotCode);
            AssertUtils.isTrue(depotOption.isPresent(), DepotException.DEPOT_NOT_EXIT);
            depotStockRos = depotStockRedisDao.getByDepotCodesAndProductCodes(channelCode, Lists.newArrayList(depotCode), productCodes);
            DepotRespVo depotRespVo = depotOption.get();
            Long districtId = depotRespVo.getDistrictId();
            if (districtId != null) {
                depotCodes.addAll(this.getGeoDepotCodes(districtId));
            }
        } else {
            depotStockRos = new ArrayList<>();
        }
        depotCodes.addAll(homeDepots);
        depotCodes.addAll(centerDepots);
        depotCodes.remove(depotCode);
        Long version = depotStockRedisDao.getVersion(channelCode);
        List<DepotStockRo> stockRos = depotStockRedisDao.getByDepotCodesAndProductCodes(channelCode, depotCodes, productCodes);
        Map<String, Integer> depotMap = new HashMap<>(stockRos.size());
        Map<String, Integer> productToQuantityMap = new HashMap<>(depotStockRos.size());
        for (DepotStockRo stockRo : stockRos) {
            String productCode = stockRo.getProductCode();
            Integer stock = depotMap.getOrDefault(productCode, 0);
            depotMap.put(productCode, stock + this.getAvailableQuantity(stockRo, version));
        }
        for (DepotStockRo stockRo : depotStockRos) {
            String productCode = stockRo.getProductCode();
            Integer stock = productToQuantityMap.getOrDefault(productCode, 0);
            productToQuantityMap.put(productCode, stock + this.getAvailableQuantity(stockRo, version));
        }
        return productCodes.stream().map(productCode -> {
            Integer quantity = depotMap.getOrDefault(productCode, 0);
            Integer warehouseQuantity = productToQuantityMap.getOrDefault(productCode, 0);
            return new StockRespVo(productCode, quantity, warehouseQuantity);
        }).collect(Collectors.toList());
    }

    /**
     * 获取附近符合商品库存的门店及其起送价  运费模板
     */
    public List<DepotMeetStockRespVo> getNearMeetStockDepots(NearMeetStockDepotReqVo reqVo) {
        throw new RuntimeException("跑熔断");
//        BigDecimal lon = reqVo.getLon();
//        BigDecimal lat = reqVo.getLat();
//        String productCode = reqVo.getProductCode();
//        Integer quantity = reqVo.getQuantity();
//        StockChannelEnum channelCode = reqVo.getChannelCode();
//        if (lat == null || lon == null) {
//            return new ArrayList<>();
//        }
//        AssertUtils.hasLength(productCode, StockException.PRODUCT_CODE_IS_NULL);
//        AssertUtils.isTrue(quantity != null && quantity > 0, StockException.QUANTITY_IS_NULL);
//        SearchDepotConditionVo condition = new SearchDepotConditionVo();
//        condition.setLongitude(lon.doubleValue());
//        condition.setLatitude(lat.doubleValue());
//        condition.setPageable(false);
//        List<DepotRespVo> depots = depotFeignClient.findNearDepot(condition);
//        if (CollectionUtils.isEmpty(depots)) {
//            return Lists.newArrayList();
//        }
//        List<String> depotCodes = depots.stream().map(DepotRespVo::getDepotCode).collect(Collectors.toList());
//        List<DepotStockRo> depotStockRos =
//                depotStockRedisDao.getByDepotCodesAndProductCodes(channelCode, depotCodes, Lists.newArrayList(productCode));
//        if (CollectionUtils.isEmpty(depotStockRos)) {
//            return Lists.newArrayList();
//        }
//        Long version = depotStockRedisDao.getVersion(channelCode);
//        Map<String, Integer> meetStocksMap = depotStockRos.stream()
//                .filter(ro -> this.getAvailableQuantity(ro, version) > quantity)
//                .collect(Collectors.toMap(DepotStockRo::getDepotCode, ro -> this.getAvailableQuantity(ro, version)));
//        return depots.stream()
//                .map(vo -> {
//                    String dCode = vo.getDepotCode();
//                    Integer pq = meetStocksMap.getOrDefault(dCode, 0);
//                    if (pq > quantity) {
//                        DepotMeetStockRespVo respVo = new DepotMeetStockRespVo();
//                        CopyUtils.copyProperties(vo, respVo);
//                        respVo.setProductCode(productCode);
//                        respVo.setQuantity(pq);
//                        respVo.setDetailAddress(String.format("%s%s%s", vo.getProvinceBaiduName(), vo.getCityBaiduName(), vo.getAddress()));
//                        respVo.setDepotName(vo.getName());
//                        return respVo;
//                    }
//                    return null;
//                }).filter(Objects::nonNull)
//                .collect(Collectors.toList());
    }

    /**
     * 获取省内满足库存的门店库存
     *
     * @param reqVo 请求VO
     */
    public MeetStockRespVo getMeetByProvince(ProvinceStockReqVo reqVo) {
        Long provinceId = reqVo.getProvinceId();
        StockChannelEnum channelCode = reqVo.getChannelCode();
        List<String> notExpect = reqVo.getNotExpect();
        AssertUtils.notNull(channelCode, StockException.CHANNEL_CODE_IS_NULL);
        List<ProductQuantityPair> pair = reqVo.getPair();
        AssertUtils.notEmpty(pair, StockException.PRODUCT_CODE_IS_NULL);
        AssertUtils.notNull(provinceId, StockException.PROVINCE_ID_IS_NULL);
        BigDecimal lat = reqVo.getLat();
        BigDecimal lon = reqVo.getLon();
        SearchDepotConditionVo vo = new SearchDepotConditionVo();
        vo.setLatitude(lat.doubleValue());
        vo.setLongitude(lon.doubleValue());
        Integer distance = reqVo.getDistance();
        Set<String> productCodes = pair.stream().map(ProductQuantityPair::getProductCode).collect(Collectors.toSet());
        Map<String, Integer> requireQuantity = pair.stream()
                .collect(Collectors.toMap(ProductQuantityPair::getProductCode, ProductQuantityPair::getQuantity));
        int productSize = requireQuantity.size();
        vo.setDistance(distance == null ? null : distance.doubleValue());
        vo.setProvinceId(provinceId);
        List<DepotRespVo> nearDepot = depotFeignClient.findNearDepot(vo);
        if (CollectionUtils.isEmpty(nearDepot)) {
            return new MeetStockRespVo();
        }
        List<String> depotCodes = nearDepot.stream()
                .map(DepotRespVo::getDepotCode)
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(notExpect)) {
            depotCodes.removeAll(notExpect);
        }

        Long version = depotStockRedisDao.getVersion(channelCode);
        String meetDepot = null;
        for (String depotCode : depotCodes) {
            List<DepotStockRo> stockRos =
                    depotStockRedisDao.getByDepotCodeAndProductCodes(channelCode, depotCode, productCodes, true);
            List<DepotStockRo> ros = stockRos.stream().filter(ro -> {
                String productCode = ro.getProductCode();
                Integer requireStock = requireQuantity.get(productCode);
                Integer availableQuantity = this.getAvailableQuantity(ro, version);
                return availableQuantity >= requireStock;
            }).collect(Collectors.toList());
            if (Objects.equals(ros.size(), productSize)) {
                meetDepot = depotCode;
                break;
            }
        }
        return buildMeetStockRespVos(channelCode, meetDepot, productCodes, version);
    }

    /**
     * 获取立即送有无满足商品数量的门店
     *
     * @param reqVo 请求VO
     */
    public MeetStockRespVo getMeetStock(ProvinceStockReqVo reqVo) {
        SearchDepotConditionVo searchVo = new SearchDepotConditionVo();
        List<ProductQuantityPair> pair = reqVo.getPair();
        if (CollectionUtils.isEmpty(pair)) {
            return new MeetStockRespVo();
        }
        Set<String> productCodes = pair.stream().map(ProductQuantityPair::getProductCode).collect(Collectors.toSet());
        Map<String, Integer> requireQuantity = pair.stream()
                .collect(Collectors.toMap(ProductQuantityPair::getProductCode, ProductQuantityPair::getQuantity));
        StockChannelEnum channelCode = reqVo.getChannelCode();
        BigDecimal lat = reqVo.getLat();
        BigDecimal lon = reqVo.getLon();
        searchVo.setLongitude(lon.doubleValue());
        searchVo.setLatitude(lat.doubleValue());
        int productSize = requireQuantity.size();
        List<DepotRespVo> depots = depotFeignClient.findNearDepot(searchVo);
        List<String> depotCodes = Optional.ofNullable(depots).orElse(new ArrayList<>()).stream()
                .map(DepotRespVo::getDepotCode)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(depotCodes)) {
            return new MeetStockRespVo();
        }
        String meetDepot = null;
        Long version = depotStockRedisDao.getVersion(channelCode);
        for (String depotCode : depotCodes) {
            List<DepotStockRo> stockRos =
                    depotStockRedisDao.getByDepotCodeAndProductCodes(channelCode, depotCode, productCodes, true);
            if (CollectionUtils.isEmpty(stockRos)) {
                continue;
            }
            List<DepotStockRo> collect = stockRos.stream().filter(ro -> {
                String productCode = ro.getProductCode();
                Integer requireStock = requireQuantity.getOrDefault(productCode, 0);
                Integer availableQuantity = this.getAvailableQuantity(ro, version);
                return availableQuantity >= requireStock;
            }).collect(Collectors.toList());
            if (Objects.equals(collect.size(), productSize)) {
                meetDepot = depotCode;
                break;
            }
        }
        return buildMeetStockRespVos(channelCode, meetDepot, productCodes, version);
    }

    /**
     * 获取太原店满足商品数量的门店库存
     *
     * @param reqVo 请求VO
     */
    public MeetStockRespVo getMeetByHome(MeetHomeStockReqVo reqVo) {
        MeetStockRespVo meetStockRespVo = new MeetStockRespVo();
        List<String> depotCodes = this.getHomeDepots();
        if (CollectionUtils.isEmpty(depotCodes)) {
            return meetStockRespVo;
        }

        List<ProductQuantityPair> pair = reqVo.getPair();
        if (CollectionUtils.isEmpty(pair)) {
            return meetStockRespVo;
        }
        List<String> notExpect = reqVo.getNotExpect();
        if (CollectionUtils.isNotEmpty(notExpect)) {
            depotCodes.removeAll(notExpect);
        }
        Boolean meet = reqVo.getMeet();
        Boolean stockMax = reqVo.getStockMax();
        StockChannelEnum channelCode = reqVo.getChannelCode();
        List<String> productCodes = Optional.of(pair).orElse(new ArrayList<>())
                .stream()
                .map(ProductQuantityPair::getProductCode)
                .collect(Collectors.toList());
        Long version = depotStockRedisDao.getVersion(channelCode);
        String meetDepot = null;
        Integer maxNum = 0;
        if (meet) {
            Map<String, Integer> requireQuantityMap = Optional.of(pair).orElse(new ArrayList<>())
                    .stream()
                    .collect(Collectors.toMap(ProductQuantityPair::getProductCode, ProductQuantityPair::getQuantity));
            //商品种类数  一个编码认为是一个种类
            int productSize = requireQuantityMap.size();
            for (String depotCode : depotCodes) {
                List<DepotStockRo> stockRos =
                        depotStockRedisDao.getByDepotCodeAndProductCodes(channelCode, depotCode, productCodes, false);
                if (CollectionUtils.isEmpty(stockRos)) {
                    continue;
                }
                List<DepotStockRo> collect = Optional.of(stockRos).orElse(new ArrayList<>())
                        .stream()
                        .filter(ro -> {
                            String productCode = ro.getProductCode();
                            Integer availableQuantity = this.getAvailableQuantity(ro, version);
                            return availableQuantity >= requireQuantityMap.getOrDefault(productCode, 0);
                        }).collect(Collectors.toList());
                //满足库存的库存对象数和商品种类数相同，认为找到了一个满足全部商品库存的门店
                if (collect.size() == productSize) {
                    List<Integer> quantities = collect.stream()
                            .map(ro -> this.getAvailableQuantity(ro, version))
                            .collect(Collectors.toList());
                    Optional<Integer> totalOption = quantities.stream().reduce((a, i) -> a + i);
                    Integer total = totalOption.orElse(0);
                    if (total > maxNum) {
                        maxNum = total;
                        meetDepot = depotCode;
                    }
                    if (StringUtils.isNotBlank(meetDepot) && !stockMax) {
                        break;
                    }
                }
            }
        } else {
            for (String depotCode : depotCodes) {
                List<DepotStockRo> stockRos =
                        depotStockRedisDao.getByDepotCodeAndProductCodes(channelCode, depotCode, productCodes, false);
                if (CollectionUtils.isEmpty(stockRos)) {
                    continue;
                }
                //获取商品总数量
                List<Integer> quantities = stockRos.stream()
                        .map(ro -> this.getAvailableQuantity(ro, version))
                        .collect(Collectors.toList());
                Optional<Integer> totalOption = quantities.stream().reduce((a, i) -> a + i);
                Integer total = totalOption.orElse(0);
                if (total > maxNum) {
                    maxNum = total;
                    meetDepot = depotCode;
                }
                if (StringUtils.isNotBlank(meetDepot) && !stockMax) {
                    break;
                }
            }
        }
        return buildMeetStockRespVos(channelCode, meetDepot, productCodes, version);
    }

    /**
     * 获取总仓库存
     *
     * @param reqVo 请求对象VO
     */
    public List<BaseStockRespVo> getCenterStock(BaseStockReqVo reqVo) {
        List<String> centerDepots = this.getCenterDepots();
        List<String> productCodes = reqVo.getProductCodes();
        StockChannelEnum channelCode = reqVo.getChannelCode();
        AssertUtils.notNull(channelCode, StockException.CHANNEL_CODE_IS_NULL);
        AssertUtils.notEmpty(productCodes, StockException.PRODUCT_CODE_IS_NULL);
        List<DepotStockRo> stockRos = depotStockRedisDao.getByDepotCodesAndProductCodes(channelCode, centerDepots, productCodes);
        Long version = depotStockRedisDao.getVersion(channelCode);
        Map<String, Integer> map = stockRos.stream()
                .collect(Collectors.toMap(DepotStockRo::getProductCode, ro -> this.getAvailableQuantity(ro, version)));
        return productCodes.stream().map(productCode -> new BaseStockRespVo(productCode, map.getOrDefault(productCode, 0)))
                .collect(Collectors.toList());
    }

    /**
     * 获取太原店库存
     *
     * @param reqVo 请求对象VO
     */
    public List<BaseStockRespVo> getHomeStock(BaseStockReqVo reqVo) {
        List<String> homeDepots = this.getHomeDepots();
        List<String> productCodes = reqVo.getProductCodes();
        StockChannelEnum channelCode = reqVo.getChannelCode();
        AssertUtils.notNull(channelCode, StockException.CHANNEL_CODE_IS_NULL);
        AssertUtils.isTrue(CollectionUtils.isNotEmpty(productCodes), StockException.PRODUCT_CODE_IS_NULL);
        List<DepotStockRo> stockRos = depotStockRedisDao.getByDepotCodesAndProductCodes(channelCode, homeDepots, productCodes);
        Long version = depotStockRedisDao.getVersion(channelCode);
        Map<String, Integer> map = stockRos.stream()
                .collect(Collectors.toMap(DepotStockRo::getProductCode, ro -> this.getAvailableQuantity(ro, version)));
        return productCodes.stream().map(productCode -> new BaseStockRespVo(productCode, map.getOrDefault(productCode, 0)))
                .collect(Collectors.toList());
    }

    /**
     * 后台库存查询
     *
     * @param reqVo 请求对象VO
     */
    public List<BackenStockListRespVo> backenStockList(BackenStockListReqVo reqVo) {
        List<DepotStockRo> stockRos = new ArrayList<>();
        String depotCode = reqVo.getDepotCode();
        String productCode = reqVo.getProductCode();
        StockChannelEnum channelCode = reqVo.getChannelCode();
        Long version = depotStockRedisDao.getVersion(channelCode);
        if (version == null) {
            return new ArrayList<>();
        }
        if (StringUtils.isBlank(depotCode) && StringUtils.isBlank(productCode)) {
            return new ArrayList<>();
        }
        if (StringUtils.isNotBlank(depotCode) && StringUtils.isNotBlank(productCode)) {
            stockRos = depotStockRedisDao.getByDepotCodesAndProductCodes(channelCode, Lists.newArrayList(depotCode), Lists.newArrayList(productCode));
        } else if (StringUtils.isNotBlank(depotCode)) {
            stockRos = depotStockRedisDao.getStocksByDepotCode(depotCode);
        } else if (StringUtils.isNotBlank(productCode)) {
            stockRos = depotStockRedisDao.getStocksByProductCode(productCode);
        }
        return Optional.ofNullable(stockRos).orElse(new ArrayList<>()).stream().map(
                ro -> {
                    BackenStockListRespVo resp = new BackenStockListRespVo();
                    CopyUtils.copyProperties(ro, resp);
                    resp.setQuantity(this.getAvailableQuantity(ro, version));
                    return resp;
                }
        ).collect(Collectors.toList());
    }

    /**
     * 获取总仓
     */
    private List<String> getCenterDepots() {
        List<DepotRespVo> depots = depotFeignClient.getAllCenterDepots();
        return depots.stream().map(DepotRespVo::getDepotCode).collect(Collectors.toList());
    }

    /**
     * 获取太原店
     */
    private List<String> getHomeDepots() {
        List<DepotRespVo> depots = depotFeignClient.getAllHomeDepots();
        return depots.stream().map(DepotRespVo::getDepotCode).collect(Collectors.toList());
    }


    /**
     * 获取可用库存
     *
     * @param quantity       库存数量
     * @param lockedQuantity 锁库存数量
     */
    private Integer getAvailableQuantity(Integer quantity, Integer lockedQuantity) {
        quantity = quantity == null ? 0 : quantity > 0 ? quantity : 0;
        lockedQuantity = lockedQuantity == null ? 0 : lockedQuantity > 0 ? lockedQuantity : 0;
        int i = quantity - lockedQuantity;
        return i > 0 ? i : 0;
    }

    /**
     * 获取门店可用库存
     *
     * @param stockRo 门店库存对象
     * @param version 版本号
     */
    private Integer getAvailableQuantity(DepotStockRo stockRo, Long version) {
        if (stockRo == null || version.compareTo(stockRo.getVersion()) > 0) {
            return 0;
        }
        return getAvailableQuantity(stockRo.getQuantity(), stockRo.getLockedQuantity());
    }

    /**
     * 获取区域的门店
     *
     * @param geoId 区域ID
     */
    private List<String> getGeoDepotCodes(Long geoId) {
        List<DepotRespVo> depots = depotFeignClient.findDepotsByGeoId(geoId);
        if (CollectionUtils.isEmpty(depots)) {
            return new ArrayList<>();
        }
        return Optional.of(depots).orElse(new ArrayList<>())
                .stream()
                .filter(vo -> Objects.equals(vo.getDepotType(), DepotType.NORMAL))
                .map(DepotRespVo::getDepotCode)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toList());
    }

    private MeetStockRespVo buildMeetStockRespVos(StockChannelEnum channelCode, String depotCode, Collection<String> productCodes, Long version) {
        MeetStockRespVo meetStockRespVo = new MeetStockRespVo();
        if (channelCode != null
                || StringUtils.isBlank(depotCode)
                || version == null
                || CollectionUtils.isEmpty(productCodes)) {
            return meetStockRespVo;
        }
        meetStockRespVo.setFound(true);
        meetStockRespVo.setDepotCode(depotCode);
        List<DepotStockRo> stockRos =
                depotStockRedisDao.getByDepotCodeAndProductCodes(channelCode, depotCode, productCodes, false);
        List<BaseStockRespVo> collect = Optional.ofNullable(stockRos).orElse(new ArrayList<>())
                .stream()
                .map(ro -> new BaseStockRespVo(ro.getProductCode(), this.getAvailableQuantity(ro, version)))
                .collect(Collectors.toList());
        meetStockRespVo.setStockItems(collect);
        return meetStockRespVo;
    }
}